package org.thirdHero.wx.utils;

import com.github.binarywang.wxpay.constant.WxPayConstants;
import com.google.common.collect.Maps;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.lang.reflect.Field;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

public class WxSignUtils {
    private static final Logger logger = LoggerFactory.getLogger(WxSignUtils.class);

    public static final String CDATA_PREFIX = "<![CDATA[";
    public static final String CDATA_SUFFIX = "]]>";
    /**
     * 微信公众号支付签名算法(详见:https://pay.weixin.qq.com/wiki/doc/api/tools/cash_coupon.php?chapter=4_3)
     *
     * @param xmlBean  Bean需要标记有XML注解
     * @param signKey  签名Key
     * @param signType 签名类型，如果为空，则默认为MD5
     * @return 签名字符串
     */
    public static String createSign(Object xmlBean, String signKey, String signType) {
        if(xmlBean instanceof HashMap){
            return createSigns((HashMap)xmlBean, signKey, signType);
        }else{
            return createSigns(xmlBean2Map(xmlBean), signKey, signType);

        }
    }

    public static String createSigns(Map<String, String> params, String signKey, String signType) {
        SortedMap<String, String> sortedMap = new TreeMap<>(params);

        StringBuilder toSign = new StringBuilder();
        for (String key : sortedMap.keySet()) {
            String value = params.get(key);
            if (StringUtils.isNotEmpty(value)
                    && !StringUtils.equalsAny(key, "sign", "key", "sign_type")) {
                if(value.contains(CDATA_SUFFIX)){
                    value = value.replace(CDATA_PREFIX, "").replace(CDATA_SUFFIX, "");
                }
                toSign.append(key).append("=").append(value).append("&");
            }
        }
        toSign.append("key=").append(signKey);
        logger.info("签名字符串为:{}",toSign);
        if (WxPayConstants.SignType.HMAC_SHA256.equals(signType)) {
            return createHMACSha256Sign(toSign.toString(), signKey);
        } else {
            return DigestUtils.md5Hex(toSign.toString()).toUpperCase();
        }
    }

    private static String createHMACSha256Sign(String message, String key) {
        try {
            Mac hmacSHA256 = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), "HmacSHA256");
            hmacSHA256.init(secretKeySpec);
            byte[] bytes = hmacSHA256.doFinal(message.getBytes());
            return Hex.encodeHexString(bytes).toUpperCase();
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }

        return null;
    }
    /**
     * 校验签名是否正确
     *
     * @param params  需要校验的参数Map
     * @param signKey 校验的签名Key
     * @return true - 签名校验成功，false - 签名校验失败
     */
    public static boolean checkSign(Map<String, String> params, String signKey,String signType) {
        String sign = createSign(params, signKey, signType);
        return sign.equals(params.get("sign"));
    }
    /**
     * 将bean按照@XStreamAlias标识的字符串内容生成以之为key的map对象
     *
     * @param bean 包含@XStreamAlias的xml bean对象
     * @return map对象
     */
    public static Map<String, String> xmlBean2Map(Object bean) {
        Map<String, String> result = Maps.newHashMap();
        List<Field> fields = new ArrayList<>(Arrays.asList(bean.getClass().getDeclaredFields()));
        fields.addAll(Arrays.asList(bean.getClass().getSuperclass().getDeclaredFields()));
        if(bean.getClass().getSuperclass().getSuperclass()!=null){
            fields.addAll(Arrays.asList(bean.getClass().getSuperclass().getSuperclass().getDeclaredFields()));
        }
        for (Field field : fields) {
            try {
                boolean isAccessible = field.isAccessible();
                field.setAccessible(true);
                if (field.get(bean) == null) {
                    field.setAccessible(isAccessible);
                    continue;
                }

                if (field.isAnnotationPresent(XStreamAlias.class)) {
                    result.put(field.getAnnotation(XStreamAlias.class).value(), field.get(bean).toString());
                }

                field.setAccessible(isAccessible);
            } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }

        }

        return result;
    }
}
